import abc
from typing import Iterator, Iterable, AsyncIterable

import pymongo
from pymongo.collection import Collection
from motor.motor_asyncio import AsyncIOMotorCollection
from pymongo.errors import DuplicateKeyError

from database import MongoClients
from items.base import (
    BaseInsertItem,
    ObjectId,
    MyObjectId
)
from exceptions import (
    ItemNotFoundException,
)
from utils.tools import hump_to_snake


class BaseModel(abc.ABC):
    """
    操作Mongodb的基类
    """
    # 查询操作中返回的数据结构类型
    ret_item = BaseInsertItem

    def __init__(self):
        self.__collection_name = hump_to_snake(self.__class__.__name__)
        if '_model' == self.__collection_name[-6:]:
            self.__collection_name = self.__collection_name.replace('_model', '')

        self.async_col: AsyncIOMotorCollection = MongoClients.async_db[self.__collection_name]
        self.sync_col: Collection = MongoClients.sync_db[self.__collection_name]
        indexes = []
        for index in self.unique_indexes:
            indexes.append((index, pymongo.ASCENDING))

        if indexes:
            if not self.sync_col.index_information().get('unique_index'):
                self.sync_col.create_index(indexes, unique=True, name='unique_index')

    def __enter__(self):
        return self

    def __aenter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        return None

    def __aexit__(self, exc_type, exc_val, exc_tb):
        return None

    @property
    def unique_indexes(self):
        return []

    def unique_indexes_filter(self, item: ret_item):
        return {
            index: getattr(item, index)
            for index in self.unique_indexes
        }

    def format_id(self, id):
        if isinstance(id, ObjectId):
            return id
        return ObjectId(str(id))

    def sync_find(
            self,
            filter=None,
            page_num=1,
            page_size=0,
            sort_key: str | list = None,
            sort_direction=pymongo.ASCENDING
    ) -> Iterable[ret_item]:
        """
        同步多查询
        """
        default_filter = {"is_del": 0}
        if filter:
            default_filter.update(filter)
        if page_size == 0:
            cursor = self.async_col.find(default_filter)
        else:
            cursor = self.async_col.find(default_filter).limit(page_size).skip((page_num - 1) * page_size)

        if sort_key:
            cursor = cursor.sort(sort_key, sort_direction)

        for item in cursor:
            yield self.ret_item(**item)

    async def async_find(
            self,
            filter=None,
            page_num=1,
            page_size=0,
            sort_key: str | list = None,
            sort_direction=pymongo.ASCENDING
    ) -> AsyncIterable[ret_item]:
        """异步多查询"""
        default_filter = {"is_del": 0}
        if filter:
            default_filter.update(filter)
        if page_size == 0:
            cursor = self.async_col.find(default_filter)
        else:
            cursor = self.async_col.find(default_filter).limit(page_size).skip((page_num - 1) * page_size)

        if sort_key:
            cursor = cursor.sort(sort_key, sort_direction)

        async for item in cursor:
            yield self.ret_item(**item)

    def sync_find_one(self, filter):
        """同步单查询"""
        default_filter = {"is_del": 0}
        default_filter.update(filter)
        data = self.sync_col.find_one(default_filter)
        if data:
            return self.ret_item(**data)
        else:
            raise ItemNotFoundException(self.__collection_name)

    async def async_find_one(self, filter):
        """异步单查询"""
        default_filter = {"is_del": 0}
        default_filter.update(filter)
        data = await self.async_col.find_one(default_filter)
        if data:
            return self.ret_item(**data)
        else:
            raise ItemNotFoundException(self.__collection_name)

    def sync_find_one_by_id(self, id: str):
        """用 id 同步单查询"""
        return self.sync_find_one({"_id": self.format_id(id)})

    async def async_find_one_by_id(self, id: str):
        """用 id 异步单查询"""
        return await self.async_find_one({"_id": self.format_id(id)})

    def sync_insert_one(self, item: ret_item) -> ret_item:
        """同步插入数据"""
        try:
            new_data = self.sync_col.insert_one(item.dict(by_alias=True))
        except DuplicateKeyError:
            _filter = self.unique_indexes_filter(item)
            item.is_del = 0
            self.sync_col.update_one(
                _filter,
                {"$set": item.dict(
                    by_alias=True,
                    exclude_none=True,
                    exclude={'id', 'creat_time'}
                )}

            )
            created_data = self.sync_col.find_one(_filter)
        else:
            created_data = self.sync_col.find_one({"_id": new_data.inserted_id})
        return self.ret_item(**created_data)

    async def async_insert_one(self, item: ret_item) -> ret_item:
        """异步插入数据"""
        try:
            new_data = await self.async_col.insert_one(item.dict(by_alias=True))
        except DuplicateKeyError:
            filter = self.unique_indexes_filter(item)
            item.is_del = 0
            await self.async_col.update_one(
                filter,
                {"$set": item.dict(
                    by_alias=True,
                    exclude_none=True,
                    exclude={'id', 'creat_time'}
                )}
            )
            created_data = await self.async_col.find_one(filter)
        else:
            created_data = await self.async_col.find_one({"_id": new_data.inserted_id})
        return self.ret_item(**created_data)

    def sync_update_one(self, _filter, item: ret_item | dict):
        """同步更新数据"""
        exclude = set(self.unique_indexes)
        exclude.update({'id', 'create_time', 'is_del'})
        if isinstance(item, self.ret_item):
            update_data = item.dict(
                exclude_none=True,
                by_alias=True,
                exclude=exclude
            )
        else:
            update_data = item

        update_result = self.sync_col.update_one(_filter, {"$set": update_data})
        if update_result.modified_count == 1:
            data = self.sync_col.find_one(_filter)
            return self.ret_item(**data)

    async def async_update_one(self, _filter, item: ret_item | dict):
        """异步更新数据"""
        exclude = set(self.unique_indexes)
        exclude.update({'id', 'create_time', 'is_del'})

        if isinstance(item, self.ret_item):
            update_data = item.dict(
                exclude_none=True,
                by_alias=True,
                exclude=exclude
            )
        else:
            update_data = item

        update_result = await self.async_col.update_one(_filter, {"$set": update_data})
        if update_result.modified_count == 1:
            data = await self.async_col.find_one(_filter)
            return self.ret_item(**data)

    def sync_update_one_by_id(self, id: str, item: ret_item):
        """同步更新数据"""
        _filter = {"_id": self.format_id(id)}
        return self.sync_update_one(_filter, item)

    async def async_update_one_by_id(self, id: str, item: ret_item):
        """异步更新数据"""
        _filter = {"_id": self.format_id(id)}
        return await self.async_update_one(_filter, item)

    def sync_delete_one(self, id):
        """同步删除数据，软删除"""
        _filter = {"_id": self.format_id(id)}
        self.sync_col.update_one(_filter, {"$set": {"is_del": 1}})

    async def async_delete_one(self, id):
        """异步删除数据，软删除"""
        _filter = {"_id": self.format_id(id)}
        await self.async_col.update_one(_filter, {"$set": {"is_del": 1}})

    def sync_hard_delete_one(self, id):
        """同步删除数据，硬删除"""
        self.sync_col.delete_one({"_id": self.format_id(id)})

    async def async_hard_delete_one(self, id):
        """异步删除数据，硬删除"""
        await self.async_col.delete_one({"_id": self.format_id(id)})

    def sync_delete_one_by_filter(self, _filter):
        """同步删除数据，软删除"""
        self.sync_col.update_one(_filter, {"$set": {"is_del": 1}})

    async def async_delete_one_by_filter(self, _filter):
        """异步删除数据，软删除"""
        await self.async_col.update_one(_filter, {"$set": {"is_del": 1}})

    def sync_hard_delete_one_by_filter(self, _filter):
        """同步删除数据，硬删除"""
        self.sync_col.delete_one(_filter)

    async def async_hard_delete_one_by_filter(self, _filter):
        """异步删除数据，硬删除"""
        await self.async_col.delete_one(_filter)
